/*
 * Copyright (c) United Binary LLC.  All rights reserved.
 * 
 * This code is licensed under the MIT License
 * 
 * SEE: http://harnessit.codeplex.com/license
 * 
 */
#region using ...

using System;
using System.Collections;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;

#endregion

namespace UnitedBinary.Core.Windows.Forms
{
	/// <include file='Forms.xml' path='/Docs/RichTextComposition/Class/*'/>
	public class RichTextComposition
	{
		private ArrayList strings = new ArrayList( 1024 );
		private RichTextBox rtb;
		private bool ownRichTextBox = false;
		private int previousStringCount = 0;
		private Font font;

		/// <include file='Forms.xml' path='/Docs/RichTextComposition/TextAdded/*'/>
		public event EventHandler TextAdded;

		/// <include file='Forms.xml' path='/Docs/RichTextComposition/TextCleared/*'/>
		public event EventHandler TextCleared;

		/// <include file='Forms.xml' path='/Docs/RichTextComposition/TextRendered/*'/>
		public event EventHandler TextRendered;

		/// <include file='Forms.xml' path='/Docs/RichTextComposition/ctor/*'/>
		public RichTextComposition()
		{
			font = new Font( "Microsoft Sans Serif", 12 );
		}

		/// <include file='Forms.xml' path='/Docs/RichTextComposition/RichTextBox/*'/>
		public RichTextBox RichTextBox
		{
			get { return rtb; }
			set
			{
				if ( value == null )
				{
					throw new ArgumentNullException( "RichTextBox" );
				}

				rtb = value;
				OwnsRichTextBox = false;
				this.Font = rtb.Font;
			}
		}

		private bool suspendRendering = false;

		public bool SuspendRendering
		{
			get { return suspendRendering; }
			set
			{
				bool wasSuspended = suspendRendering;
				suspendRendering = value;
				if ( wasSuspended && !value )
				{
					Render();
				}
			}
		}

		/// <include file='Forms.xml' path='/Docs/RichTextComposition/Render/*'/>
		public void Render()
		{
			if ( rtb == null )
			{
				throw new ArgumentNullException( "RichTextBox control is null" );
			}

			if (SuspendRendering)
            {
				return;
            }

			int pos = rtb.SelectionStart;

			if ( OwnsRichTextBox )
			{
				RenderOwned();
			}
			else
			{
				RenderShared();
			}

			if ( pos >= 0 )
			{
				rtb.Select( pos, 0 );
			}

			if ( TextRendered != null )
			{
				TextRendered( this, null );
			}
		}

		/// <include file='Forms.xml' path='/Docs/RichTextComposition/RenderAll/*'/>
		public void RenderAll()
		{
			//
			// Force drawing to start with first string.
			//
			previousStringCount = 0;
			rtb.Clear();
			Render();
		}

		[DllImport("user32.dll")]
		private static extern IntPtr SendMessage(
			IntPtr hWnd, // handle to destination window
			UInt32 Msg, // message
			UIntPtr wParam, // first message parameter
			IntPtr lParam // second message parameter
			);

		private void RenderOwned()
		{
			bool invalidate = false;
			uint WM_SETREDRAW = 11;
			try
			{
				SendMessage( rtb.Handle, WM_SETREDRAW, UIntPtr.Zero, IntPtr.Zero );

				int strstart = 0;

				if ( ModifiedSinceLastRender )
				{
					rtb.Clear();
				}
				else
				{
					strstart = previousStringCount;
				}

				RichTextString s;
				int length = strings.Count;
				for ( int i = strstart; i < length; i++ )
				{
					s = (RichTextString)strings[i];

					int start = rtb.Text.Length;
					rtb.AppendText( s.Text );

					rtb.Select( start, s.Length );
					rtb.SelectionColor = s.Color;
					rtb.SelectionFont = s.Font;

					int pos = rtb.SelectionStart;

					if ( pos != -1 )
					{
						rtb.Select( pos, 0 );
					}

					invalidate = true;
					s.Modified = false;
				}

				previousStringCount = length;
			}
			finally
			{
				UIntPtr pOne = new UIntPtr( 1 );
				SendMessage( rtb.Handle, WM_SETREDRAW, pOne, IntPtr.Zero );

				if ( invalidate )
				{
					rtb.Invalidate( true );
				}
			}
		}

		private void RenderShared()
		{
			uint WM_SETREDRAW = 11;
			try
			{
				SendMessage( rtb.Handle, WM_SETREDRAW, UIntPtr.Zero, IntPtr.Zero );

				RichTextString s;
				int length = strings.Count;
				for ( int i = 0; i < length; i++ )
				{
					s = (RichTextString)strings[i];

					int start = rtb.Text.Length;

					rtb.AppendText( s.Text );

					rtb.Select( start, s.Length );
					rtb.SelectionColor = s.Color;
					rtb.SelectionFont = s.Font;
				}
			}
			finally
			{
				UIntPtr pOne = new UIntPtr( 1 );
				SendMessage( rtb.Handle, WM_SETREDRAW, pOne, IntPtr.Zero );
				rtb.Invalidate( true );
			}
		}

		private bool ModifiedSinceLastRender
		{
			get
			{
				int count = previousStringCount;

				if ( strings.Count < count || previousStringCount == 0 )
				{
					return true;
				}

				foreach ( RichTextString rtString in strings )
				{
					if ( rtString.Modified )
					{
						return true;
					}
				}

				return false;
			}
		}

		/// <include file='Forms.xml' path='/Docs/RichTextComposition/Add_string/*'/>
		public void Add(string text)
		{
			RichTextString rts = new RichTextString( text, (Font)Font.Clone() );
			Add( rts );
		}

		/// <include file='Forms.xml' path='/Docs/RichTextComposition/Add_RTS/*'/>
		public void Add(RichTextString richText)
		{
			strings.Add( richText );

			if ( TextAdded != null )
			{
				TextAdded( this, null );
			}
		}

		/// <include file='Forms.xml' path='/Docs/RichTextComposition/AddLine_string/*'/>
		public void AddLine(string text)
		{
			if ( text == null )
			{
				throw new ArgumentNullException( "text" );
			}

			Add( text + "\n" );
		}

		/// <include file='Forms.xml' path='/Docs/RichTextComposition/AddLine_RTS/*'/>
		public void AddLine(RichTextString richText)
		{
			if ( richText == null )
			{
				throw new ArgumentNullException( "richText" );
			}

			richText.Text = richText.Text + "\n";
			Add( richText );
		}

		/// <include file='Forms.xml' path='/Docs/RichTextComposition/Clear/*'/>
		public void Clear()
		{
			Strings = null;
			if ( TextCleared != null )
			{
				TextCleared( this, null );
			}
		}

		/// <include file='Forms.xml' path='/Docs/RichTextComposition/Strings/*'/>
		public RichTextString[] Strings
		{
			get
			{
				int length = strings.Count;
				RichTextString[] retval = new RichTextString[length];
				for ( int i = 0; i < length; i++ )
				{
					retval[i] = (RichTextString)strings[i];
				}
				return retval;
			}
			set
			{
				strings.Clear();
				if ( value == null )
				{
					return;
				}

				RichTextString[] strings1 = value;

				int length = strings1.Length;
				for ( int i = 0; i < length; i++ )
				{
					strings.Add( strings1[i] );
				}
			}
		}

		/// <include file='Forms.xml' path='/Docs/RichTextComposition/Length/*'/>
		public int Length
		{
			get
			{
				int length = 0;
				foreach ( RichTextString s in strings )
				{
					length += s.Length;
				}

				return length;
			}
		}

		/// <include file='Forms.xml' path='/Docs/RichTextComposition/OwnsRichTextBox/*'/>
		public bool OwnsRichTextBox
		{
			get { return ownRichTextBox; }
			set { ownRichTextBox = value; }
		}

		/// <include file='Forms.xml' path='/Docs/RichTextComposition/Font/*'/>
		public Font Font
		{
			get { return font; }
			set
			{
				if ( font == null )
				{
					throw new ArgumentNullException( "Font" );
				}

				font = value;
			}
		}

		/// <include file='Forms.xml' path='/Docs/RichTextComposition/operator_plus/*'/>
		public static RichTextComposition operator +(RichTextComposition richComp1, RichTextComposition richComp2)
		{
			if ( richComp1 == null )
			{
				throw new ArgumentNullException( "richComp1" );
			}
			if ( richComp2 == null )
			{
				throw new ArgumentNullException( "richComp2" );
			}

			RichTextComposition rtNew = new RichTextComposition();
			RichTextString[] strings1 = richComp1.Strings;
			RichTextString[] strings2 = richComp2.Strings;

			int length1 = strings1.Length;
			int length2 = strings2.Length;

			for ( int i = 0; i < length1; i++ )
			{
				rtNew.Add( strings1[i] );
			}

			for ( int i = 0; i < length2; i++ )
			{
				rtNew.Add( strings2[i] );
			}

			return rtNew;
		}
	}
}